package com.fredhat.tvshowrenamer.parser.tvcom;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

import com.fredhat.tvshowrenamer.TVShow;
import com.fredhat.tvshowrenamer.TVShowEpisode;
import com.fredhat.tvshowrenamer.parser.TVParser;

public class TVComParser implements TVParser {
	private Logger logger;
	
	public TVComParser() {
		logger = Logger.getLogger(this.getClass());
	}
	
	public TVShowEpisode getEpisodeFromAirDate(TVShow tvShow, Calendar airDate) {
		logger.debug("Getting episode ID of "+tvShow.getName()+" air date "+airDate.getTime());

		// Try cache first
		TVComCache cache = new TVComCache();
		if (cache.getTVComEpisodeByAirdate(tvShow, airDate) != null) {
			logger.debug("Fetching value from cache");
			TVComEpisode episode = cache.getTVComEpisodeByAirdate(tvShow, airDate);
			
			TVShowEpisode tvShowEpisode = new TVShowEpisode(episode.getSeasonNumber(), 
					episode.getEpisodeNumber(), episode.getTitle(), tvShow);
			logger.debug("Returning TVShowEpisode: "+episode);
			return tvShowEpisode;
		}

		logger.debug("Fetching value from tv.com remotely");
		int season = 1;
		while (true) {
			logger.debug("Getting episode list from season "+season);
			List<TVComEpisode> seasonList = getSeason(tvShow, season);
			if (seasonList.size() == 0) {
				logger.debug("No episode found for air date "+airDate.getTime());
				return null;
			}
			for(int i=0; i<seasonList.size(); i++) {
				TVComEpisode tvComEpisode = seasonList.get(i);
				logger.debug("Comparing TVComEpisode "+tvComEpisode);
				Calendar epAirdate = tvComEpisode.getAirDate();
				if (epAirdate.get(Calendar.MONTH) == airDate.get(Calendar.MONTH) &&
						epAirdate.get(Calendar.DATE) == airDate.get(Calendar.DATE) &&
						epAirdate.get(Calendar.YEAR) == airDate.get(Calendar.YEAR)) {
					TVShowEpisode episode = new TVShowEpisode(season, i+1, 
							tvComEpisode.getTitle(), tvShow);
					logger.debug("Found calendar match.  Returning episode: "+episode);
					return episode;
				}
			}
			
			season++;
		}		
	}

	public TVShowEpisode getEpisode(TVShow tvShow, int seasonNumber, int episodeNumber) {
		if (episodeNumber < 1) {
			logger.error("Illegal episode value \""+episodeNumber+"\" for "+
					tvShow.getName()+" season "+seasonNumber+
					".  Episode must be 1 or greater.");
			return null;
		}
		
		List<TVComEpisode> episodes = getSeason(tvShow, seasonNumber);
		if (episodeNumber > episodes.size()) {
			logger.error("Episode number "+episodeNumber+" doesn't exist for "+
					tvShow.getName()+" season "+seasonNumber+".  There were only "+
					episodes.size()+" episodes parsed.");
			return null;
		}

		TVComEpisode tvComEpisode = episodes.get(episodeNumber-1);
		return new TVShowEpisode(seasonNumber, episodeNumber, 
				tvComEpisode.getTitle(), tvShow);
	}
	
	private List<TVComEpisode> getSeason(TVShow tvShow, int seasonNumber) {
		TVComCache cache = new TVComCache();
		URL url = null;
		try {
			url = new URL(tvShow.getTvComURL()+"/episode_listings.html?season="+
					seasonNumber);
		} catch (MalformedURLException e) {
			logger.error("Couldn't build URL for "+tvShow.getName()+" season "+
					seasonNumber, e);
			return null;
		}
		
		logger.debug("Parsing URL for "+tvShow.getName()+" episodes for season "+
				seasonNumber+": "+url);
		Vector<TVComEpisode> episodes = new Vector<TVComEpisode>();
		int episodeNumber = 1;
		int cumNumber = 0; // Not tracking this yet
		String title = null;
		Calendar airDate = getNewCalendar();
		try {
			BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
			String str;
			Pattern titlePattern = Pattern.compile(
					".*?<a href.*?/episode/.*?/summary\\.html.*?title.*?>(.*?)</a>.*?");
			Pattern datePattern = Pattern.compile("^\\s*(\\d+/\\d+/\\d+).*?");
			SimpleDateFormat dateFormatter = new SimpleDateFormat("MM/dd/yyyy"); 
			while ((str = in.readLine()) != null) {
				Matcher titleMatcher = titlePattern.matcher(str);
				Matcher dateMatcher = datePattern.matcher(str);
				
				if (titleMatcher.matches()) {
					title = titleMatcher.group(1);
					logger.debug("Found title: "+title);
					logger.debug("From string: "+str);
				}
				
				if (dateMatcher.matches()) {
					String dateStr = dateMatcher.group(1);
					logger.debug("Found date string: "+dateStr);
					logger.debug("From string: "+str);
					Date date = null;
					try {
						date = dateFormatter.parse(dateStr);
					} catch (ParseException e) {
						logger.error("Couldn't parse date for "+url+": "+dateStr);
						title = null;
						airDate = getNewCalendar();
						episodeNumber++;
						continue;
					}
					
					airDate.setTime(date);
					if (title == null)
						logger.warn("No title for show "+tvShow.getName()+" season "+
								seasonNumber+" episode "+episodeNumber);
					TVComEpisode tvComEpisode = new TVComEpisode(seasonNumber, 
							episodeNumber, cumNumber, title, airDate);
					
					// Add value to cache tables
					TVShowEpisode tvShowEpisode = new TVShowEpisode(seasonNumber, 
							episodeNumber, title, tvShow);
					cache.setTVComEpisodeByShowEpisode(tvShow, tvComEpisode);
					cache.setTVComEpisodeByAirdate(tvShow, airDate, tvComEpisode);
					
					episodes.add(tvComEpisode);
					logger.debug("Episode object built.");
					
					title = null;
					airDate = getNewCalendar();
					episodeNumber++;
				}
			}
			in.close();
		} catch (IOException e) {
			logger.error("Couldn't get data from URL "+url, e);
			return null;
		}
		
		return episodes;
	}
	
	private Calendar getNewCalendar() {
		Calendar cal = Calendar.getInstance();
		cal.set(Calendar.YEAR, 1970);
		cal.set(Calendar.MONTH, 0);
		cal.set(Calendar.DATE, 1);
		cal.set(Calendar.HOUR, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		
		return cal;
	}
}
